package com.seven.annotation;

import com.google.common.collect.Maps;
import com.seven.cache.CacheApi;
import com.seven.commons.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @工程名:common-utils
 * @author: sevenDay
 * @时间: 2019/4/4 14:06
 * @描述:
 */
@Aspect
@Component
@Order(3)
@Slf4j
public class SevenCacheComponent extends BaseComponent {


    private static final Logger LOGGER = LoggerFactory.getLogger(SevenCacheComponent.class);

    private final String annotation = "@within(com.seven.annotation.SevenCache) || @annotation(com.seven.annotation.SevenCache))";

    private final static String LIST = "java.util.List";
    /**
     * 必须要有一个地方实现cache的接口然后注入的时候才能实现
     */
    @Autowired(required = false)
    private CacheApi cacheApi;

    @Pointcut(annotation)
    public void cachePoint() {
    }

    @Around("cachePoint()")
    public Object handleCache(ProceedingJoinPoint pjp) throws Throwable {
        Method m = ((MethodSignature) pjp.getSignature()).getMethod();
        Boolean isList = false;
        String obj = null;
        Class<?> clazz = null;
        try {
            Method method = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes());
            //获取定义seven的注解
            SevenCache sevenCache = method.getDeclaredAnnotation(SevenCache.class);
            //获取序列化结果的值
            ParseObject parseObject = new ParseObject();
            //设置的key
            parseObject.setKey(sevenCache.key());
            //设置的expire time
            parseObject.setExpireTime(sevenCache.expire());
            //设置是否重新计算过期时间
            parseObject.setReset(sevenCache.reset());
            //设置是否缓存
            parseObject.setEmpty(sevenCache.empty());
            //设置空值缓存时间
            parseObject.setEmptyExpireTime(sevenCache.expireEmpty());
            //设置默认值
            parseObject.setDefaultValue(sevenCache.defaultValue());
            //获取返回值类型
            isList = false;
            clazz = getReturenType(method, isList);
            //判断是否返回List的值
            parseObject.setList(isList);
            //设置返回类型
            parseObject.setClazz(clazz);
            Object[] as = pjp.getArgs();
            if (log.isDebugEnabled()) {
                log.info("before parse key: ====>[{}]", parseObject.getKey());
            }
            parseObject.setKey(parseKey(method, as, parseObject.getKey()));
            if (log.isDebugEnabled()) {
                log.info("after parse  key: ====>[{}]", parseObject.getKey());
            }
            String v = (String) cacheApi.get(parseObject.getKey());
            parseObject.setValue(v);
            return empty(parseObject, pjp);
        } catch (Throwable e) {
            throwException(e);
        }
        return null;
    }

    private Object empty(ParseObject parseObject, ProceedingJoinPoint pjp) throws Throwable {
        if (StringUtils.isBlank(parseObject.getValue())) {
            Object result = pjp.proceed();
            if (result != null) {
                if (!Objects.isNull(result)) {
                    cacheApi.put(parseObject.getKey(), JsonUtils.objectToJson(result), parseObject.getExpireTime());
                }
                return result;
            } else {
                if (StringPool.NULL.equals(parseObject.getDefaultValue())) {
                    if (parseObject.isEmpty()) {
                        cacheApi.put(parseObject.getKey(), parseObject.getDefaultValue(), parseObject.getEmptyExpireTime());
                    }
                    return null;
                } else {
                    if (parseObject.isEmpty()) {
                        cacheApi.put(parseObject.getKey(), parseObject.getDefaultValue(), parseObject.getEmptyExpireTime());
                    }
                    if (parseObject.isList()) {
                        return JsonUtils.jsonToList(parseObject.getDefaultValue(), parseObject.getClazz());
                    } else {
                        return JsonUtils.jsonToPojo(parseObject.getDefaultValue(), parseObject.getClazz());
                    }
                }

            }
        } else if (StringPool.NULL.equals(parseObject.getValue())) {
            //进行为空的处理和判断
            return getResult(parseObject,true);
        } else {
            //当值不为空或者null的时候返回
            return getResult(parseObject,false);
        }

    }

    private Object getResult(ParseObject parseObject,boolean isNullStr){
        if (parseObject.isList()) {
            if (parseObject.isReset()) {
                cacheApi.put(parseObject.getKey(), parseObject.getValue(), parseObject.getExpireTime());
                return isNullStr?null:JsonUtils.jsonToList(parseObject.getValue(), parseObject.getClazz());
            } else {
                return isNullStr?null:JsonUtils.jsonToList(parseObject.getValue(), parseObject.getClazz());
            }

        } else {

            if (parseObject.isReset()) {
                cacheApi.put(parseObject.getKey(), parseObject.getValue(), parseObject.getExpireTime());
                return isNullStr?null:JsonUtils.jsonToPojo(parseObject.getValue(), parseObject.getClazz());
            } else {
                return isNullStr?null:JsonUtils.jsonToPojo(parseObject.getValue(), parseObject.getClazz());
            }
        }
    }


    /**
     * @param method     : 方法名
     * @param argValues  : 参数
     * @param expression : 属性
     * @return : java.lang.String
     * @descrp: namespace_#{category.getCategoryCacheKey()}_#{category.getKey()}
     * @auther: Xiaohong
     * @date: 2019/4/4 15:06
     */
    public String parseKey(Method method, Object[] argValues, String expression) {
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (paramNames == null && expression.contains(StringPool.HASH) && (!parseRules(expression))) {
            //当参数列表为空的时候，但是传递的字符产有#分割的动态注入的时候就是失败
            throw new AnnotationException(AnnotationEnum.CACHE_EXECUTE_ERROR);
        }
        if (expression.contains(StringPool.HASH)) {
            final List<String> lists = ExtractMessageUtils.extractMessageByRegular(expression);
            StringBuilder buffer = new StringBuilder();
            if (!expression.startsWith(StringPool.HASH)) {
                //首字符是否是#开头的字符串
                /* List<String> lists = GuavaUtils.separatorString(expression, StringPool.HASH);*/
                if (CollectionUtils.isNotEmpty(lists)) {
                    buffer = StringUtils.isBlank(lists.get(0)) ? buffer.append("") : buffer.append(lists.get(0));
                }
                //进行参数的动态打印观察,参数列表显示
                List<String> paramsList = Arrays.asList(paramNames);
                List<Object> objs = Arrays.asList(argValues);
                if (log.isDebugEnabled()) {
                    log.debug("============打印参数============");
                    paramsList.forEach(x -> log.debug(x));
                    objs.forEach(x -> log.debug(String.valueOf(x)));
                    lists.forEach(x -> log.debug(String.valueOf(x)));
                }
                Map<String, String> map = parseParamsfromargs(objs, paramsList, lists);
                for (String x : lists) {
                    String value = map.get(x);
                    String target = StringPool.HASH_LEFT_BRACE + x + StringPool.RIGHT_BRACE;
                    expression = expression.replace(target, value);
                }
            }
        }
        return expression.replace(StringPool.HASH_LEFT_BRACE, StringPool.EMPTY).replace(StringPool.RIGHT_BRACE, StringPool.EMPTY);
    }


    private boolean parseRules(String expression) {

        int leftCount = 0;
        int rightCount = 0;
        char[] chars = expression.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == '{') {
                leftCount++;
            } else if (chars[i] == '}') {
                rightCount++;
            }
        }
        return leftCount == rightCount ? true : false;
    }

    public Class<?> getReturenType(Method method, Boolean col) {

        //返回类型的class
        Class classType = method.getReturnType();
        //字符串匹配 java.util.List<java.lang.Integer>
        String methodString = method.getGenericReturnType().toString();

        if (methodString.contains(LIST)) {
            col = true;
        }
        return classType;
    }

    /**
     * ["type","name","orderInfo.getOrderNo()"]
     * ["type","orderInfo","name"]
     * [1,{"orderNo":"s123456"},"zhangfuhong"]
     * ============打印参数============
     * type
     * orderInfo
     * name
     * ============打印参数的的值============
     * 1
     * OrderInfo(orderNo=s123456, orderStatus=null)
     * zhangfuhong
     * name==>zhangfuhong
     *
     * @param objs
     * @param params
     * @param exPressString
     */
    private Map<String, String> parseParamsfromargs(List<Object> objs, List<String> params, List<String> exPressString) {
        Map<String, String> paramObj = Maps.newHashMap();
        for (int i = 0; i < objs.size(); i++) {
            Object obj = objs.get(i);
            if (BeanUtils.isPrimitive(obj)) {
                //是基本类型的不用处理
                paramObj.put(params.get(i), String.valueOf(obj));
            } else {
                //不是基本类型的进行方法的调用
                String p = params.get(i);
                exPressString.forEach(x -> {
                    if (x.startsWith(p)) {
                        //查找到了相关的数据信息
                        List<String> lists = GuavaUtils.separatorString(x, StringPool.DOT);
                        String tempMetheodName = lists.get(1);
                        String methodString = tempMetheodName.substring(0, tempMetheodName.length() - 2);
                        try {
                            Method m = obj.getClass().getMethod(methodString);
                            Object obj1 = null;
                            try {
                                obj1 = m.invoke(obj);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                            String resultString = obj1.toString();
                            paramObj.put(x, resultString);
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }
                    }
                });

            }
        }
        return paramObj;
    }

    @Data
    class ParseObject {
        /**
         * 返回数据类型
         */
        private Class<?> clazz;
        /**
         * 是否重新设置，不支持为空的设置
         */
        private boolean reset;
        /**
         * 返回值类型
         */
        private boolean isList;
        /**
         * 缓存的key
         */
        private String key;
        /**
         * 缓存的值value
         */
        private String value;
        /**
         * 是否缓存控制，在缓存击穿的时候
         */
        private boolean empty;
        /**
         * 正常的key的缓存时间毫秒
         */
        private long expireTime;
        /**
         * 缓存为空值得过期时间
         */
        private long emptyExpireTime;
        /**
         * 设置的默认值
         */
        private String defaultValue;
    }

    public static void main(String[] args) {
        String str = "namespace_#{category.getCategoryCacheKey()}_#{category.getKey()}";
        String m = "category.getCategoryCacheKey()";
        String[] s = m.split("\\.");
        String params = s[0];
        System.out.println(params);
        String methodString = s[1].substring(0, s[1].length() - 2);
        System.out.println(methodString);

    }

}
